﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using SemenQuest.Communication;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using Message = SemenQuest.Communication.Message;

namespace SemenQuest.Models
{
    public class Semen : Agent
    {
        #region Variables
        private static String[] FemaleNames { get; set; }
        private static String[] MaleNames { get; set; }

        public Genders Gender { get; private set; }
        public enum Genders { Male = 1, Female = 2 }

        private Int32 CurrentPic = 0;
        private Int32 AnimationRefreshCount { get; set; }
        private Int32 AnimationRefreshRate { get; set; }
        /// <summary>
        /// First is Image, Second is Angle
        /// </summary>
        private Image[][] Images
        {
            get
            {
                return AnimationImages[Gender];
            }
        }
        private static Dictionary<Genders, Image[][]> AnimationImages { get; set; }
        private static Dictionary<Genders, LoadingStates> AnimationProcess { get; set; }
        private enum LoadingStates { NotLoaded = 0, Loading = 1, Loaded = 2 }

        private static Boolean StaticSettingsDone { get; set; }

        private System.Windows.Forms.Timer TargetTimer;
        private IContainer components;

        private Guid? Leader { get; set; }
        private List<Guid> Followers { get; set; }

        private enum AgentStates
        {
            Spawning,

            Searching,

            SwimmingTowardEgg,
            Impregnating,

            InTeam,
            InTeamAsLeader,

            Dying,
            Won,
        }
        private AgentStates AgentState;

        private Point Target { get; set; }
        private Int32 ImpregNr { get; set; }
        private Guid? TargetEgg { get; set; }

        private Boolean BoidAdjusting { get; set; }
        #endregion

        #region Constructor
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.TargetTimer = new System.Windows.Forms.Timer(this.components);
            this.SuspendLayout();
            // 
            // TargetTimer
            // 
            this.TargetTimer.Interval = new Random().Next(250, 600);
            this.TargetTimer.Tick += new System.EventHandler(this.TargetTimer_Tick);
            this.ResumeLayout(false);

        }
        public Semen()
            : base()
        {
            InitializeComponent();

            //Static settings setup
            LoadStaticSettings();

            #region Gender & Name
            if (DateTime.Now.Millisecond % 2 == 0)
            {
                Gender = Genders.Male;
                Name = MaleNames[new Random().Next(0, MaleNames.Length)];
            }
            else
            {
                Gender = Genders.Female;
                Name = FemaleNames[new Random().Next(0, FemaleNames.Length)];
            }
            #endregion

            #region Load Settings
            MaxVelocity = Properties.Settings.Default.SemenMaxVelocity;
            Visibility = Properties.Settings.Default.SemenVisibility;
            TurnSpeed = Properties.Settings.Default.SemenTurnSpeed;
            Acceleration = Properties.Settings.Default.SemenAcceleration;
            BreakSpeed = Properties.Settings.Default.SemenBreakSpeed;
            LifeTime = Properties.Settings.Default.SemenLifeTime;
            MinDistance = Properties.Settings.Default.SemenMinDistance;
            #endregion
            SettingsController.SemenSettingChanged += Agent_SettingsUpdate;

            #region Load Animations
            lock (AnimationProcess)
            {
                if (AnimationProcess[Gender] == LoadingStates.NotLoaded)
                {
                    Color color = (Color)Properties.Settings.Default.GetType()
                        .GetProperty("SemenColor" + Enum.GetName(typeof(Genders), Gender))
                        .GetValue(Properties.Settings.Default);
                    LoadAnimation(Gender, color);
                }
            }
            #endregion

            this.DoubleBuffered = true;
            this.SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.UserPaint,
                true);
            this.UpdateStyles();

            AgentState = AgentStates.Spawning;
            AgentType = AgentTypes.Semen;
            Angle = new Random().Next(0, 360);
            Thread.Sleep(8);
            TargetAngle = new Random().Next(0, 360);
            Velocity = 100;

            this.Size = new System.Drawing.Size(13, 35);

            Followers = new List<Guid>();

            TickSubscribe(See);     //Perceive the world
            TickSubscribe(Main);    //Decide what to do
            TickSubscribe(Animate); //Update graphics
            TickSubscribe(Move);    //React to the world

            NewMessage += HandleMessage;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void LoadStaticSettings()
        {
            if (StaticSettingsDone)
                return;
            StaticSettingsDone = true;

            //Semen Agent Names
            LoadNames();

            //Animation images
            SettingsController.SemenSettingChanged += Semen_SettingsUpdate;

            AnimationProcess = new Dictionary<Genders, LoadingStates>();
            AnimationImages = new Dictionary<Genders, Image[][]>();

            foreach (Genders gender in Enum.GetValues(typeof(Genders)))
            {
                AnimationProcess.Add(gender, LoadingStates.NotLoaded);
                Animator.AnimatorComplete += AnimatorComplete;
            }
        }
        private static void Semen_SettingsUpdate(String settingName, Object value)
        {
            if (settingName == "SemenColorMale")
            {
                Color newColor = (Color)value;
                LoadAnimation(Genders.Male, newColor);
            }
            else if (settingName == "SemenColorFemale")
            {
                Color newColor = (Color)value;
                LoadAnimation(Genders.Female, newColor);
            }
        }
        private static void LoadNames()
        {
            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("SemenQuest.Resources.MaleNames.txt"))
            using (StreamReader reader = new StreamReader(stream))
            {
                List<String> Names = new List<String>();
                while (!reader.EndOfStream)
                {
                    Names.Add(reader.ReadLine());
                }
                MaleNames = Names.ToArray();
            }

            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("SemenQuest.Resources.FemaleNames.txt"))
            using (StreamReader reader = new StreamReader(stream))
            {
                List<String> Names = new List<String>();
                while (!reader.EndOfStream)
                {
                    Names.Add(reader.ReadLine());
                }
                FemaleNames = Names.ToArray();
            }
        }
        #endregion

        #region ImagesGenerator
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void LoadAnimation(Genders gender, Color color)
        {
            //1. get base models
            Image[] baseModel = GetImages(gender);

            //2. change color of models
            for (Int32 i = 0; i < baseModel.Length; i++)
            {
                String oldColorString = (String)Properties.Settings.Default.Properties["SemenColor" + Enum.GetName(typeof(Genders), gender)].DefaultValue;
                Color oldColor = ImageUtils.TranslateString(oldColorString);
                baseModel[i] = ImageUtils.ChangeColor(baseModel[i], oldColor, color);
            }

            //3. Start Animator
            Animator.Start(gender, color, baseModel);
            AnimationProcess[gender] = LoadingStates.Loading;
        }
        private static Image[] GetImages(Genders Gender)
        {
            List<Image> images = new List<Image>();
            Int32 x = 1;
            while (true)
            {
                String path = "SemenQuest.Resources.Semen_" + Gender + "_" + x++ + ".png";
                try
                {
                    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path))
                    {
                        Image image = Image.FromStream(stream);
                        (image as Bitmap).MakeTransparent((image as Bitmap).GetPixel(0, 0));
                        images.Add(image);
                    }
                }
                catch
                {
                    break;
                }
            }
            return images.ToArray();
        }
        private static class Animator
        {
            private class AnimatorWorker
            {
                public Genders Gender { get; private set; }
                public Color Color { get; private set; }
                public Boolean CancellationPending { get { return Workers.Any(x => x.CancellationPending); } }
                public Boolean IsBusy { get { return Workers.Any(x => x.IsBusy); } }
                private List<BackgroundWorker> Workers { get; set; }
                private Image[][] Result { get; set; }
                private Image[] Data { get; set; }

                public static event AnimatorCompleteHandler AnimatorWorkerCompleted;

                public AnimatorWorker(Genders gender, Color color, Image[] baseModels)
                {
                    Data = baseModels;
                    Gender = gender;
                    this.Color = color;
                    Workers = new List<BackgroundWorker>();
                    Result = new Image[Data.Length][];
                    for (Int32 imageNr = 0; imageNr < Data.Length; imageNr++)
                    {
                        var Worker = new BackgroundWorker();
                        Worker.RunWorkerCompleted += WorkerComplete;
                        Worker.DoWork += GenerateImages;
                        Worker.WorkerSupportsCancellation = true;
                        Workers.Add(Worker);
                        AnimatorWorkerCompleted += AnimatorWorkerComplete;
                        Worker.RunWorkerAsync(imageNr);
                    }
                }

                public void CancelAsync()
                {
                    Workers.ForEach(x => x.CancelAsync());
                }
                private void GenerateImages(Object sender, DoWorkEventArgs e)
                {
                    Image image = Data[(Int32)e.Argument];
                    Image[] result = new Image[360];
                    for (Int32 i = 0; i < 360; i++)
                    {
                        result[i] = ImageUtils.RotateImage(image, (i + 90) % 360);
                        if (e.Cancel)
                        {
                            return;
                        }
                    }
                    lock (Result)
                    {
                        Result[(Int32)e.Argument] = result;
                    }
                }
                private void WorkerComplete(Object sender, RunWorkerCompletedEventArgs e)
                {
                    if (e.Cancelled)
                    {
                        return;
                    }

                    var handler = AnimatorWorkerCompleted;
                    if (!IsBusy && handler != null && !CancellationPending)
                        handler(Gender, Result);
                }
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public static void Start(Genders gender, Color color, Image[] baseModels)
            {
                //1. create class properties
                if (AnimationWorkers == null)
                    AnimationWorkers = new Dictionary<Genders, AnimatorWorker>();

                //2. if animator is working on a set of images, abort it
                Monitor.Enter(AnimationWorkers);
                if (AnimationWorkers.ContainsKey(gender))
                {
                    AnimationWorkers[gender].CancelAsync();
                    Monitor.Exit(AnimationWorkers);
                    while (AnimationWorkers.ContainsKey(gender)) Thread.Yield();
                }
                if (Monitor.IsEntered(AnimationWorkers))
                    Monitor.Exit(AnimationWorkers);

                //3. create animator
                AnimationWorkers.Add(gender, new AnimatorWorker(gender, color, baseModels));
            }

            private static Dictionary<Genders, AnimatorWorker> AnimationWorkers { get; set; }

            public delegate void AnimatorCompleteHandler(Genders gender, Image[][] result);
            public static event AnimatorCompleteHandler AnimatorComplete;

            private static void AnimatorWorkerComplete(Genders gender, Image[][] result)
            {
                lock (AnimationWorkers)
                {
                    AnimationWorkers.Remove(gender);
                }
                var handler = AnimatorComplete;
                if (handler != null)
                    handler(gender, result);
            }
        }
        private static void AnimatorComplete(Genders gender, Image[][] result)
        {
            if (AnimationImages.ContainsKey(gender))
            {
                AnimationImages[gender] = result;
            }
            else
            {
                AnimationImages.Add(gender, result);
            }
            AnimationProcess[gender] = LoadingStates.Loaded;
        }
        #endregion

        #region Overrides
        public override void RunAgent(Boolean randomLocation)
        {
            //delay start if animation images is not loaded
            if (AnimationImages.ContainsKey(Gender))
            {
                base.RunAgent(randomLocation);
                AgentState = AgentStates.Searching;
                return;
            }

            //wait 50 ms and try again
            System.Timers.Timer timer = new System.Timers.Timer(50);
            timer.SynchronizingObject = this;
            timer.AutoReset = false;
            timer.Elapsed += delegate(Object sender, ElapsedEventArgs e)
            {
                timer.Dispose();
                RunAgent(randomLocation);
            };
            timer.Start();
        }
        protected override void KillAgent(Agent.KillReasons killReason)
        {
            SettingsController.SemenSettingChanged -= this.Agent_SettingsUpdate;
            base.KillAgent(killReason);
        }
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x20;
                return cp;
                //return base.CreateParams;
            }
        }
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            Int32 PicInt = (360 + (Int32)Angle) % 360;
            Image nextPic = Images[CurrentPic][PicInt];
            this.Size = nextPic.Size;
            e.Graphics.DrawImage(nextPic, 0, 0);

            if (Properties.Settings.Default.DebugShowBoidAdjust)
            {
                if (BoidAdjusting)
                {
                    e.Graphics.FillRectangle(new SolidBrush(Color.Green), 0, 0, 3, 3);
                }
                else
                {
                    e.Graphics.FillRectangle(new SolidBrush(Color.Red), 0, 0, 3, 3);
                }
            }
        }
        #endregion

        #region Communications
        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage(Conversation conversation)
        {
            Communication.Message message = conversation.ReturnNewest();
            if (message == null)
            {
                return;
            }

            switch (message.Type)
            {
                case Communication.Message.MessageTypes.Inform:
                    HandleMessage_Inform(message);
                    break;

                case Communication.Message.MessageTypes.Request:
                    HandleMessage_Request(message);
                    break;

                case Communication.Message.MessageTypes.Agree:
                    HandleMessage_Agree(message);
                    break;

                case Communication.Message.MessageTypes.Refuse:
                    HandleMessage_Refuse(message);
                    break;

                case Communication.Message.MessageTypes.NotUnderstood:
                    break;

                case Communication.Message.MessageTypes.Unresponsive:
                    return;

                default:
                    break;

            }

        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage_Inform(SemenQuest.Communication.Message message)
        {
            switch (message.Content)
            {
                case Communication.Message.Contents.EggFound:
                    #region Egg Found
                    switch (AgentState)
                    {
                        case AgentStates.Searching:
                            this.Target = (Point)message.AdditionalInformation;
                            AgentState = AgentStates.SwimmingTowardEgg;
                            break;



                        case AgentStates.SwimmingTowardEgg:
                            if (MathUtils.DistanceToTarget(this.Location, (Point)message.AdditionalInformation) < MathUtils.DistanceToTarget(this.Location, Target))
                            {
                                this.Target = (Point)message.AdditionalInformation;
                            }
                            break;



                        case AgentStates.InTeam:
                            //Inform the leader of the team
                            if (Leader != null)
                            {
                                Agent leaderAgent = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == Leader);

                                if (leaderAgent != null)
                                {
                                    SendMessage(leaderAgent, new Communication.Message(
                                        this.Id,
                                        leaderAgent.Id,
                                        Communication.Message.MessageTypes.Inform,
                                        Communication.Message.Contents.EggFound,
                                        message.AdditionalInformation));
                                }
                                else
                                {
                                    Leader = null;
                                    AgentState = AgentStates.Searching;
                                }
                            }
                            break;



                        case AgentStates.InTeamAsLeader:
                            this.Target = (Point)message.AdditionalInformation;
                            break;



                        default:
                            break;
                    }
                    #endregion
                    break;

                case Communication.Message.Contents.EggImpregnated:
                    TargetEgg = null;
                    AgentState = AgentStates.Searching;

                    break;

                case Communication.Message.Contents.YouImpregnatedTheEgg:
                    AgentState = AgentStates.Won;
                    break;

                case Communication.Message.Contents.YouDidNotImpregnateTheEgg:
                    TargetEgg = null;
                    AgentState = AgentStates.Searching;
                    break;

                default:
                    break;
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage_Request(Communication.Message message)
        {
            if (message.Content == Communication.Message.Contents.SwimTogether)
            {
                var sender = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == message.Sender);
                if (sender == null)
                {
                    return;
                }

                switch (AgentState)
                {
                    case AgentStates.Searching:
                        AgentState = AgentStates.InTeam;
                        Leader = message.Sender;
                        this.Target = sender.Location;

                        SendMessage(sender, new Communication.Message(
                            this.Id,
                            message.Sender,
                            Communication.Message.MessageTypes.Agree,
                            Communication.Message.Contents.SwimTogether));
                        break;



                    case AgentStates.InTeamAsLeader:
                        Followers.Add(message.Sender);
                        goto case AgentStates.InTeam;
                    case AgentStates.InTeam:
                        SendMessage(sender, new Communication.Message(
                            this.Id,
                            message.Sender,
                            Communication.Message.MessageTypes.Agree,
                            Communication.Message.Contents.AlreadyInTeam,
                            Leader));
                        break;



                    default:
                        SendMessage(sender, new Communication.Message(
                            this.Id,
                            message.Sender,
                            Communication.Message.MessageTypes.Refuse,
                            Communication.Message.Contents.Nothing));
                        break;
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage_Agree(Communication.Message message)
        {
            Conversation cons;
            lock (Conversations)
            {
                Conversations.TryGetValue(message.Sender, out cons);
            }

            if (cons != null && cons.Length > 1)
            {
                Communication.Message lastMessage = cons[cons.Length - 2];

                if (lastMessage.Type == Communication.Message.MessageTypes.Request)
                {
                    if (lastMessage.Content == Communication.Message.Contents.SwimTogether)
                    {
                        if (message.Content == Communication.Message.Contents.AlreadyInTeam)
                        {
                            //Already in team, ask the leader, Guid given as additional information
                            Guid leaderGuid = (Guid)message.AdditionalInformation;
                            if (leaderGuid != null)
                            {
                                if (message.Sender != leaderGuid)
                                {
                                    Agent leaderAgent = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == leaderGuid);
                                    if (leaderAgent != null)
                                    {
                                        SendMessage(leaderAgent, new Communication.Message(
                                            this.Id,
                                            message.Sender,
                                            Communication.Message.MessageTypes.Request,
                                            Communication.Message.Contents.SwimTogether));
                                    }
                                }
                                else
                                { //The leader of the team answered and accepted to swim together, add leaderAgent as Leader
                                    Leader = leaderGuid;
                                    AgentState = AgentStates.InTeam;
                                }
                            }
                        }
                        else //Not in team, this agent is the new leader
                        {
                            Followers.Add(message.Sender);
                            Leader = this.Id;
                            AgentState = AgentStates.InTeamAsLeader;
                        }
                    }
                    else if (message.Content == Communication.Message.Contents.Impregnate)
                    {
                        AgentState = AgentStates.Impregnating;
                        if (message.AdditionalInformation != null)
                        {
                            ImpregNr = (Int32)message.AdditionalInformation;
                        }
                        //MaxTurnSpeed = 360;
                    }
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage_Refuse(Communication.Message message)
        {
            TargetEgg = null;
            AgentState = AgentStates.Searching;
        }
        #endregion

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void Main(Object sender, ElapsedEventArgs e)
        {
            switch (AgentState)
            {
                case AgentStates.Dying:
                    TargetTimer.Stop();
                    KillAgent(KillReasons.LifeTimeEnded);
                    break;
                case AgentStates.Won:
                    TargetTimer.Stop();
                    KillAgent(KillReasons.LotteryWon);
                    break;
                case AgentStates.Searching:
                    #region Searching
                    if (!TargetTimer.Enabled)
                    {
                        TargetTimer.Start();
                        TargetTimer_Tick(this, EventArgs.Empty);
                    }
                    //if (MathUtils.GetDistanceToTarget(Location, Target) < Visibility || Target == Point.Empty)
                    //{
                    //    TargetTimer.Start();
                    //    TargetTimer_Tick(this, EventArgs.Empty);
                    //}
                    break;
                    #endregion
                case AgentStates.SwimmingTowardEgg:
                    #region Swimming Toward Egg
                    if (TargetEgg != null)
                    {
                        Agent egg = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == TargetEgg);
                        if (egg != null)
                        {
                            Target = egg.Location;
                            TargetTimer.Stop();

                            Boolean hasSentRequest = false;
                            lock (Conversations)
                            {
                                Conversation con;
                                if (Conversations.TryGetValue(egg.Id, out con))
                                {
                                    Communication.Message lastMessage = con.ReturnNewest();
                                    if (lastMessage != null &&
                                        lastMessage.Content == Communication.Message.Contents.Impregnate &&
                                        (lastMessage.Type == Communication.Message.MessageTypes.Request || lastMessage.Type == Communication.Message.MessageTypes.Agree))
                                    {
                                        hasSentRequest = true;
                                    }
                                }
                            }

                            if (!hasSentRequest && MathUtils.DistanceToTarget(Location, Target) < 50)
                            {
                                SendMessage(egg, new Communication.Message(
                                    this.Id,
                                    egg.Id,
                                    Communication.Message.MessageTypes.Request,
                                    Communication.Message.Contents.Impregnate));
                            }
                            break;
                        }
                    }
                    TargetEgg = null;
                    AgentState = AgentStates.Searching;
                    break;
                    #endregion
                case AgentStates.Impregnating:
                    #region Impregnating
                    if (TargetEgg != null)
                    {
                        Agent egg = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == TargetEgg);
                        if (egg != null)
                        {
                            Target = egg.Location;
                            TargetTimer.Stop();
                            break;
                        }
                    }

                    //if egg is not found;
                    TargetEgg = null;
                    AgentState = AgentStates.Searching;
                    break;
                    #endregion
                case AgentStates.InTeam:
                    #region InTeam
                    if (Leader != null)
                    {
                        Agent leaderSemen = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == Leader);
                        if (leaderSemen != null && ((Semen)leaderSemen).AgentState == AgentStates.InTeamAsLeader)
                        {
                            Target = leaderSemen.Location;
                            TargetTimer.Stop();
                            break;
                        }
                        else
                        {
                            Leader = null;
                        }
                    }

                    if (TargetEgg != null)
                    {
                        AgentState = AgentStates.SwimmingTowardEgg;
                    }
                    else
                    {
                        AgentState = AgentStates.Searching;
                    }
                    break;
                    #endregion
                case AgentStates.InTeamAsLeader:
                    #region InTeamAsLeader
                    if (TargetEgg != null)
                    {
                        Agent egg = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == TargetEgg);
                        if (egg != null)
                        {
                            Target = egg.Location;
                            TargetTimer.Stop();
                            break;
                        }
                        else
                        {
                            TargetEgg = null;
                        }
                    }
                    TargetTimer.Start();
                    break;
                    #endregion
                default:
                    TargetTimer.Stop();
                    throw new NotSupportedException("Agent State not supported");
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private new void Move(Object sender, ElapsedEventArgs e)
        {
            if (AgentState == AgentStates.Impregnating)
            {
                //Target contains location of egg
                Agent egg = AgentController.GetAgentCount().SingleOrDefault(a => a.Id == TargetEgg);
                if (egg != null)
                {
                    Point impregnatingLocation = egg.CenterPoint;
                    switch (ImpregNr)
                    {
                        case 0:
                            Angle = 0;
                            impregnatingLocation.Offset(-egg.Width / 2 - this.Width, -this.Height / 2);
                            break;
                        case 1:
                            Angle = 270;
                            impregnatingLocation.Offset(-this.Width / 2, egg.Height / 2);
                            break;
                        case 2:
                            Angle = 180;
                            impregnatingLocation.Offset(egg.Width / 2, -this.Height / 2);
                            break;
                        case 3:
                            Angle = 90;
                            impregnatingLocation.Offset(-this.Width / 2, -egg.Height / 2 - this.Height);
                            break;
                        default:
                            break;
                    }
                    try
                    {
                        this.BeginInvoke(new Action(delegate
                        {
                            if (!this.IsDisposed)
                            {
                                this.Location = impregnatingLocation;
                                this.Invalidate();
                            }
                        }));
                    }
                    catch { }
                }
                else
                {
                }
                return;
            }

            Double Interval = 1000 / TickTime;
            #region Set new Velocity
            if (Velocity < TargetVelocity)
            {
                Velocity += (Double)Acceleration / Interval;
                if (Velocity < MaxVelocity * 8 / 100)
                {
                    Velocity = MaxVelocity * 8 / 100;
                }
                if (Velocity > TargetVelocity)
                {
                    Velocity = TargetVelocity;
                }

            }
            else if (Velocity > TargetVelocity)
            {
                Velocity -= (Double)BreakSpeed / Interval;
                if (Velocity < MaxVelocity * 8 / 100)
                {
                    Velocity = 0;
                }
                if (Velocity < TargetVelocity)
                {
                    Velocity = TargetVelocity;
                }
            }



            #endregion

            #region Set new Angle
            if (Angle != TargetAngle)
            {
                //Turn left or right
                Double RestAngle = (Angle - TargetAngle + 360) % 360;
                Boolean Positive = RestAngle > 180;

                //get distance in degrees
                if (Positive)
                {
                    RestAngle = 360 - RestAngle;
                }

                //Close to the target?
                if (RestAngle < TurnSpeed / Interval)
                {
                    Angle = TargetAngle;
                }
                else
                {
                    if (Positive)
                    {
                        Angle += (Double)TurnSpeed / Interval;
                    }
                    else
                    {
                        Angle -= (Double)TurnSpeed / Interval;
                    }
                    Angle = Angle % 360;
                }


                //So close that we hit target now, then turn to target
                if ((RestAngle - (Double)TurnSpeed / Interval) < 0)
                {
                    Angle = TargetAngle;
                }
            }
            #endregion

            #region Set new location
            Point newLocation = MathUtils.NewLocation(Location, Angle, Velocity / Interval);
            try
            {
                #region Bounce off the left and right wall
                if (newLocation.X <= 0)
                {
                    Angle = MathUtils.HorizontalAngleOut(newLocation, Location);
                    TargetAngle = Angle;
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / Interval);
                    while (newLocation.X <= 0) newLocation.X++;
                }
                else if (this.Parent.Size.Width <= newLocation.X + Size.Width)
                {
                    Angle = MathUtils.HorizontalAngleOut(newLocation, Location);
                    TargetAngle = Angle;
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / Interval);
                    while (this.Parent.Size.Width <= newLocation.X + Size.Width) newLocation.X--;
                }
                #endregion

                #region Bounce off the top and bottom wall
                if (newLocation.Y <= 0)
                {

                    Angle = MathUtils.VerticalAngleOut(newLocation, Location);
                    TargetAngle = Angle;
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / Interval);
                    while (newLocation.Y <= 0) newLocation.Y++;
                }
                else if (this.Parent.Size.Height <= newLocation.Y + Size.Height)
                {
                    Angle = MathUtils.VerticalAngleOut(newLocation, Location);
                    TargetAngle = Angle;
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / Interval);
                    while (this.Parent.Size.Height <= newLocation.Y + Size.Height) newLocation.Y--;
                }
                #endregion
            }
            catch { }

            try
            {
                this.Invoke(new Action(delegate
                {
                    if (!this.IsDisposed)
                    {
                        this.Location = newLocation;
                        this.Invalidate();
                    }
                }));
            }
            catch { }
            #endregion
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void See(Object sender, ElapsedEventArgs e)
        {

            //If we are dying or impregnating, then we do not care about other agent or eggs
            if (AgentState == AgentStates.Dying ||
                AgentState == AgentStates.Impregnating ||
                AgentState == AgentStates.Spawning)
            {
                return;
            }

            var agents = AgentController.GetNearbyAgents(this);

            #region Eggs
            Egg closestEgg = null;
            Double smallestDistance = Double.MaxValue;
            //Find the egg that is closest to the agent itself
            foreach (Egg egg in agents.Where(x => x.AgentType == AgentTypes.Egg))
            {
                Double newDistance = MathUtils.DistanceToTarget(this.Location, egg.Location);
                if (newDistance < smallestDistance)
                {
                    smallestDistance = newDistance;
                    closestEgg = egg;
                }
            }
            if (closestEgg != null)
            {
                if (closestEgg.MaxImpregnators != closestEgg.SemenAttached) //if room on egg, swim towards
                {
                    if (AgentState == AgentStates.Searching)
                    {
                        AgentState = AgentStates.SwimmingTowardEgg;
                    }
                    else if (AgentState == AgentStates.InTeamAsLeader)
                    {
                        Target = closestEgg.Location;
                    }
                    TargetEgg = closestEgg.Id;
                }
            }
            #endregion

            #region Semen
            //Send a message to all the other close agent, informing that an egg has been found
            if (closestEgg != null)
            {
                foreach (Semen semen in agents.Where(x => x.AgentType == AgentTypes.Semen))
                {
                    SendMessage(semen, new Communication.Message(
                        this.Id,
                        semen.Id,
                        Communication.Message.MessageTypes.Inform,
                        Communication.Message.Contents.EggFound,
                        closestEgg.Location));
                }
            }
            else //Send message to all semen swimming the same way to swim together
            {
                if (AgentState == AgentStates.Searching || AgentState == AgentStates.InTeamAsLeader)
                {
                    foreach (Semen semen in agents.Where(x => x.AgentType == AgentTypes.Semen)
                        .Where(x => MathUtils.AngleDifference(x.Angle, this.Angle) < 30))
                    {
                        if (!Followers.Contains(semen.Id)) //Don't contact followers
                        {
                            SendMessage(semen, new Communication.Message(
                                this.Id,
                                semen.Id,
                                Communication.Message.MessageTypes.Request,
                                Communication.Message.Contents.SwimTogether));
                        }
                    }
                }
            }
            #endregion

            #region Boid
            TargetVelocity = MaxVelocity;
            if (AgentState == AgentStates.InTeam ||
                TargetEgg.HasValue)
            {
                TargetAngle = MathUtils.AngleTowardTarget(this.CenterPoint, Target);
            }

            List<Vector> vectors = new List<Vector>();
            vectors.Add(new Vector(TargetAngle, 100));
            foreach (Agent agent in agents)
            {
                if (agent.AgentType == AgentTypes.Egg)
                {
                    Egg egg = (Egg)agent;
                    if (egg.SemenAttached != egg.MaxImpregnators) // if egg is filled, avoid!
                    {
                        continue;
                    }

                    if (egg.Id == TargetEgg) //if swimming toward egg, ignore
                    {
                        continue;
                    }
                }
                //ignore semen who is impregnating
                else if (agent.AgentType == AgentTypes.Semen)
                {
                    Semen semen = (Semen)agent;
                    if (semen.AgentState == AgentStates.Impregnating && AgentState == AgentStates.SwimmingTowardEgg)
                    {
                        continue;
                    }
                }
                Double distance = MathUtils.DistanceToTarget(CenterPoint, agent.CenterPoint);

                //Collission imminient??
                if (distance < MinDistance) //steer away
                {
                    Vector vector = new Vector(MathUtils.AngleTowardTarget(agent.CenterPoint, CenterPoint), 100);
                    vectors.Add(vector);
                    if (Math.Abs(vector.Invert().Angle - Angle) < 20)
                    {
                        TargetVelocity = 100;
                        continue;
                    }
                }
            }

            #region Avoid Walls
            if (this.Parent != null)
            {
                Random rand = new Random();
                //Left Wall
                if (this.CenterPoint.X < this.MinDistance)
                    if (MathUtils.AngleDifference(TargetAngle, 180) < rand.Next(75, 100))
                    {
                        vectors.Add(new Vector(0, 100));
                    }

                //Right Wall
                if ((this.Parent.Width - this.CenterPoint.X) < this.MinDistance)
                    if (MathUtils.AngleDifference(TargetAngle, 0) < rand.Next(75, 100))
                    {
                        vectors.Add(new Vector(180, 100));
                    }

                //Top Wall
                if (this.CenterPoint.Y < this.MinDistance)
                {
                    if (MathUtils.AngleDifference(TargetAngle, 270) < rand.Next(75, 100))
                    {
                        vectors.Add(new Vector(90, 100));
                    }
                }

                //Bottom Wall
                if ((this.Parent.Height - this.CenterPoint.Y) < this.MinDistance)
                {
                    if (MathUtils.AngleDifference(TargetAngle, 90) < rand.Next(75, 100))
                    {
                        vectors.Add(new Vector(270, 100));
                    }
                }
            }
            #endregion

            if (vectors.Count > 1)//If changes need
            {
                BoidAdjusting = true;

                Vector vector = new Vector()
                {
                    X = vectors.Sum(x => x.X),
                    Y = vectors.Sum(y => y.Y),
                };
                TargetAngle = MathUtils.AngleTowardTarget(Point.Empty, vector.ToPoint());
            }
            else
            {
                BoidAdjusting = false;
            }
            #endregion

        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void Animate(Object sender, ElapsedEventArgs e)
        {
            Double RefreshRate = 1000 / TickTime;
            Int32 RefreshSpeed = 5;
            if (Velocity != 0)
            {
                AnimationRefreshRate = (Int32)(RefreshRate / Velocity * MaxVelocity * RefreshSpeed / 100);
            }
            else
            {
                AnimationRefreshRate = (Int32)((RefreshRate / (MaxVelocity * RefreshSpeed / 100)) * (MaxVelocity * RefreshSpeed / 100));
            }
            AnimationRefreshCount++;
            if (AnimationRefreshCount >= AnimationRefreshRate)
            {
                CurrentPic++;
                CurrentPic = CurrentPic % 2;
                AnimationRefreshCount = 0;
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void TargetTimer_Tick(Object sender, EventArgs e)
        {
            var random = new Random();
            TargetAngle += random.Next(-30, 31); //nudge direction by a few degrees (max not included, there fore the +1)
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        protected override void LifeEnded()
        {
            AgentState = AgentStates.Dying;
        }
    }
}
